/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package udpclient;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import libudp.Ack;
import libudp.Serializer;
import libudp.Syn;

/**
 * The UDPClient class is a socket-based, datagram client. This class opens a
 * new socket on a specified port to send and receive datagram packets.
 * 
 * UDPClient sends numbered packets to a server. It then listens for incoming
 * ACKs which contain the next requested sequence number. UDPClient then creates
 * a new SYN object with the requested sequence number and forwards that.
 * @author davidmessing
 */
public class UDPClient {
    
    private InetAddress address;    // address of server
    private DatagramSocket socket = null;   // local socket which will send/recieve datagrams
    private int portClient = 6501;  // port number of client
    private int portServer = 6500;  // port number of server
    DatagramPacket packet;  // packet object to send recieve data on socket
    byte[] buf; // buffer to hold packet data
    int sequenceNum = 0;    // keep track of the next sequence number to send
    private int timeout = 5; //  socket timeout in milliseconds
    
    /**
     * Public constructor for UDPClient class. Calling this creates a new client
     * which looks for a UDPServer on localhost.
     * @throws IOException
     */
    public UDPClient() throws IOException {
        this("local", 6500, 10);
    }
    
    /**
     * Public constructor for UDPClient class. Caller specifies IP Address of
     * server and timeout period for client.
     * @param serverAddress
     * @param port 
     * @param timeoutPeriod
     * @throws IOException
     */
    public UDPClient(String serverAddress, int port, int timeoutPeriod) throws IOException {
        // configure InetAddress 
        if (serverAddress.equals("local")) {
            address = InetAddress.getLocalHost();   // init InetAddress with localhost
        }
        else {
            address = InetAddress.getByName(serverAddress);   // init InetAddress with specified IP address
        }
        
        socket = new DatagramSocket(portClient);  // open a new socket
        portServer = port;  // assign port number of server
        int timeoutCount = 0;
        timeout = timeoutPeriod;    // assign timeout period for socket
        
        do {
            if (timeoutCount == 3) {
                break;
            }  // if we timeout 3 times, exit the run loop
            
            Syn p = new Syn(System.currentTimeMillis(), sequenceNum, "SYN");  // create Syn to send
            buf = Serializer.serialize(p);  // convert Syn to byte[]
            packet = new DatagramPacket(buf, buf.length, address, portServer);    // init DatagramPacket
            socket.send(packet);    // send packet
            
            buf = new byte[256];    // clear the buffer
            packet = new DatagramPacket(buf, buf.length);   // init a new DatagramPacket
            
            socket.setSoTimeout(timeout);   // set the socket timeout
            try {
                socket.receive(packet); // receive data
                timeoutCount = 0;   // reset timeout counter
                timeout = timeoutPeriod; // reset timeout period
                
                // convert datagram packet data to Syn object
                Ack a = convertByteToAck(packet.getData());  // deserialize byte[] to Syn object
                System.out.println("UDPClient: tc2=" + System.currentTimeMillis() + ", received: " + a);  // print Ack object
                sequenceNum = a.getSequenceNum();
            } catch (SocketTimeoutException e) {
                timeoutCount++;
                System.out.println("UDPClient: Timeout " + timeoutCount + ", timeout period: " + timeout + "ms");                
                timeout *= 2;
            }            
            
        } while(sequenceNum <= 100);
        
        socket.close(); // close the socket
    }
    
    private Ack convertByteToAck(byte[] buffer) {
        Ack a = null;  
        try {
            a = (Ack) Serializer.deserialize(buffer); // deserialize byte[] to Syn object
        } catch (IOException | ClassNotFoundException ex) {
            Logger.getLogger(UDPClient.class.getName()).log(Level.SEVERE, null, ex);
        }
        return a;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        try {
            if (args.length == 3) { 
                System.out.println("IP Address: " + args[0] + ":" + args[1]);
                System.out.println("Timeout(ms): " + args[2]);
                
                new UDPClient(args[0], Integer.parseInt(args[1]), Integer.parseInt(args[2]));  // run from command line with args specified
            }
            else {
                new UDPClient();    // run with default args (localhost)
            }
            
            
        } catch (IOException ex) {
            Logger.getLogger(UDPClient.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
